package com.wctj.api.base.utils.lambda;

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.injector.AbstractMethod;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.apache.ibatis.mapping.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.*;

/**
 * 用于支持自定义Wrapper查询，
 * 当方法满足时获取sql text后与Wrapper参数(作为动态条件)结合重新生成对应的mappedStatement,
 * sql text主要是作为基本查询语句,也支持xml中bind、include标签
 */
public class QuerySupportMethod extends AbstractMethod {

    protected Logger logger = LoggerFactory.getLogger(getClass());

    protected final Map<Class, Map<String, Method>> supportMethodMap = new HashMap<Class, Map<String, Method>>(16);

    protected final List<QuerySupportProperty> propertyList = new ArrayList<QuerySupportProperty>(16);

    /**
     * mapperClass对应的builderAssistant
     */
    protected final Map<Class, MapperBuilderAssistant> mapperClassBuilderAssistantMap = new HashMap<Class, MapperBuilderAssistant>(16);

    @Resource
    protected QueryConfigProperty queryConfigProperty;

    public void setQueryConfigProperty(QueryConfigProperty queryConfigProperty) {
        this.queryConfigProperty = queryConfigProperty;
    }

    public class QuerySupportProperty {
        private Class<?> mapperClass;
        private Class<?> modelClass;
        private TableInfo tableInfo;

        public QuerySupportProperty() {
        }

        public QuerySupportProperty(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
            this.mapperClass = mapperClass;
            this.modelClass = modelClass;
            this.tableInfo = tableInfo;
        }

        public Class<?> getMapperClass() {
            return mapperClass;
        }

        public void setMapperClass(Class<?> mapperClass) {
            this.mapperClass = mapperClass;
        }

        public Class<?> getModelClass() {
            return modelClass;
        }

        public void setModelClass(Class<?> modelClass) {
            this.modelClass = modelClass;
        }

        public TableInfo getTableInfo() {
            return tableInfo;
        }

        public void setTableInfo(TableInfo tableInfo) {
            this.tableInfo = tableInfo;
        }
    }



    /**
     * 获取当前mapper所支持的查询方法并进行缓存
     *
     * @param mapperClass
     * @param modelClass
     * @param tableInfo
     * @return
     */
    protected String[] getSupportMethods(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        QuerySupport support = AnnotationUtils.findAnnotation(mapperClass, QuerySupport.class);
        Set<String> methodList = new HashSet<String>(16);

        boolean loadClassMethod;
        if (support != null) {
            loadClassMethod = true;
            for (String value : support.value()) {
                methodList.add(value);
            }
        } else {
            loadClassMethod = !this.queryConfigProperty.isClassShouldAnnotation();
        }

        if (loadClassMethod) {
            Method[] methods = ReflectionUtils.getAllDeclaredMethods(mapperClass);
            for (Method method : methods) {
                support = AnnotationUtils.findAnnotation(method, QuerySupport.class);
                String methodName = method.getName();
                if (support != null) {
                    methodList.add(methodName);
                }
                //存入support map中
                if (methodList.contains(methodName)) {
                    putSupportMap(mapperClass, method);
                }
            }
        }
        return methodList.toArray(new String[methodList.size()]);
    }

    /**
     * 缓存支持的方法
     *
     * @param mapperClass
     * @param method
     */
    public void putSupportMap(Class<?> mapperClass, Method method) {
        Map<String, Method> methodMap;
        if (supportMethodMap.containsKey(mapperClass)) {
            methodMap = supportMethodMap.get(mapperClass);
        } else {
            methodMap = new HashMap<String, Method>(16);
            supportMethodMap.put(mapperClass, methodMap);
        }
        methodMap.put(method.getName(), method);
    }

    public Method getSupportMethod(Class<?> mapperClass, String name) {
        Map<String, Method> methodMap = supportMethodMap.get(mapperClass);
        if (methodMap != null) {
            return methodMap.get(name);
        }
        return null;
    }

    public String getMethodSqlByAnnotation(Class<?> mapperClass, String name) {
        return getMethodSqlByAnnotation(mapperClass, name, getSupportMethod(mapperClass, name));
    }

    public String getMethodSqlByAnnotation(Class<?> mapperClass, String name, Method selectMethod) {
        String result = null;
        if (queryConfigProperty.isSqlWithAnnotation()) {
            String text = "Query Support find " + mapperClass.getName() + "." + name + " sql ";
            logger.debug(text + "by @Select");
            String error = text + "by @Select has error, cause by : ";
            Assert.notNull(selectMethod, error + "can't find method");
            Select select = AnnotationUtils.findAnnotation(selectMethod, Select.class);
            Assert.notNull(select, error + "not use @Select");
            try {
                StringBuilder sb = new StringBuilder();
                for (String value : select.value()) {
                    sb.append(value + "\t");
                }
                result = sb.toString();
            } catch (Exception e) {
                logger.warn(error + " {}", e.getMessage());
            }
        }

        return result;
    }

    public String getSqlText(SqlSource sqlSource) {
        return QuerySupportUtils.getSqlText(sqlSource);
    }

    protected String getSqlWhereEntityWrapperResult(TableInfo tableInfo) {
        Method method = QuerySupportUtils.findMethod(this, "sqlWhereEntityWrapper", new Class[] {TableInfo.class});
        if (method != null) {
            return (String) QuerySupportUtils.invokeMethod(this, method, tableInfo);
        }
        method = QuerySupportUtils.findMethod(this, "sqlWhereEntityWrapper", new Class[] {boolean.class, TableInfo.class});
        return (String) QuerySupportUtils.invokeMethod(this, method, true, tableInfo);
    }

    /**
     * 重新注入@QuerySupport相关的方法
     */
    protected void injectQuerySupportMappedStatement(QuerySupportProperty property) {
        Class<?> mapperClass = property.getMapperClass();
        Class<?> modelClass = property.getModelClass();
        TableInfo tableInfo = property.getTableInfo();

        String[] methodList = getSupportMethods(mapperClass, modelClass, tableInfo);
        String mapperClassName = mapperClass.getName();
        for (String method : methodList) {
            String statementName = mapperClassName + StringPool.DOT + method;
            Method selectMethod = getSupportMethod(mapperClass, method);
            Assert.notNull(selectMethod, "Query Support can't find method about " + statementName);
            boolean hasWrapper = false;
            for (Class parameterType : selectMethod.getParameterTypes()) {
                if (Wrapper.class.isAssignableFrom(parameterType)) {
                    hasWrapper = true;
                    break;
                }
            }

            MappedStatement mappedStatement = null;
            SqlSource sqlSource = null;
            try {
                mappedStatement = configuration.getMappedStatement(statementName);
                sqlSource = mappedStatement.getSqlSource();
            } catch (Exception e) {
                logger.warn("", e);
            }
            Assert.notNull(mappedStatement, "Query Support can't find statement about " + statementName);

            boolean buildSqlSource = false;

            String currentSql = null;
            if (sqlSource == null) {
                buildSqlSource = true;
                currentSql = getMethodSqlByAnnotation(mapperClass, method, selectMethod);
            } else {
                //when wrapper exist, should find sql text
                if (hasWrapper) {
                    buildSqlSource = true;
                    currentSql = getSqlText(sqlSource);
                }
            }
            if (buildSqlSource) {
                Assert.isTrue(StringUtils.isNotEmpty(currentSql), "Query Support can't correct find sql text about " + statementName);
                //logger.debug("Query Support find {} sql text: \r\n\t {}", statementName, currentSql);
                if (hasWrapper) {
                    String scriptSql;

                    //是否支持列
                    boolean isContainsColumns = currentSql.toLowerCase().matches(".*select.*%s.*from.*");

                    if (currentSql.contains("<script>")) {
                        int index = currentSql.indexOf("</script>");
                        scriptSql = currentSql.substring(0, index) + " %s " + currentSql.substring(index);
                    } else {
                        scriptSql = "<script>\n" + currentSql + "\n %s </script>";
                    }
                    //生成支持查询条件的sql, mapper方法中需提供参数 @Param(Constants.WRAPPER) Wrapper<User> wrapper

                    String sqlWhereEntityWrapperText = getSqlWhereEntityWrapperResult(tableInfo);
                    if (isContainsColumns) {
                        //增加列的sql解析
                        currentSql = String.format(scriptSql, this.sqlSelectColumns(tableInfo, true), sqlWhereEntityWrapperText);
                    } else {
                        currentSql = String.format(scriptSql, sqlWhereEntityWrapperText);
                    }
                }
                sqlSource = languageDriver.createSqlSource(configuration, currentSql, modelClass);
            }

            try {
                //修改sqlSource及resultMap
                QuerySupportUtils.setFieldValue(mappedStatement, "sqlSource", sqlSource);
                QuerySupportUtils.setFieldValue(mappedStatement, "resultMaps", getStatementResultMaps(property, selectMethod, statementName));

                logger.debug("Query Support afresh inject {} statement success", statementName);
            } catch (Exception e) {
                logger.warn("Query Support afresh inject statement error, cause by : {}", e.getMessage());
                logger.warn("", e);
            }
        }
    }



    /**
     * 启动注入
     */
    public void injectQuerySupportMappedStatement() {
        if (propertyList.isEmpty()) {
            logger.warn("Query Support afresh inject statement not find about mapper data, if has mapper data and want to use QuerySupport, please use with QuerySupportSqlInjector");
        } else {
            for (QuerySupportProperty property : propertyList) {
                injectQuerySupportMappedStatement(property);
            }
            clear();
            logger.info("Query Support afresh inject statement has completed");
        }
    }

    protected void clear() {
        if (queryConfigProperty.isClearPropertyList()) {
            propertyList.clear();
        }
        if (queryConfigProperty.isClearMapperClassBuilderAssistantMap()) {
            mapperClassBuilderAssistantMap.clear();
        }
        if (queryConfigProperty.isClearSupportMethodMap()) {
            supportMethodMap.clear();
        }
    }



    /**
     * 获取所应用的resultMap/resultType
     * @param mapperClass
     * @param id
     * @param resultType
     * @param table
     * @return
     */
    protected Object getMethodResultMap(Class<?> mapperClass, String id, Class<?> resultType, TableInfo table) {
        Method method = getSupportMethod(mapperClass, id);
        return getMethodResultMap(method, resultType, table, mapperClass);
    }

    protected Object getMethodResultMap(Method method, Class<?> resultType, TableInfo table, Class mapperClass) {
        return QuerySupportUtils.getMethodResultMap(method, resultType, table, mapperClass);
    }

    /**
     * 获取当前方法所应用的resultMaps
     * @param property
     * @param selectMethod
     * @param id
     * @return
     */
    protected List<ResultMap> getStatementResultMaps(QuerySupportProperty property, Method selectMethod, String id) {
        MapperBuilderAssistant mapperBuilderAssistant = getMapperBuilderAssistant(property.getMapperClass());
        Object methodResultMap = getMethodResultMap(selectMethod, property.getModelClass(), property.getTableInfo(), property.getMapperClass());
        String resultMap = null;
        Class resultType = null;
        if (methodResultMap instanceof Class) {
            resultType = (Class<?>) methodResultMap;
        } else {
            resultMap = (String) methodResultMap;
        }
        return getStatementResultMaps(mapperBuilderAssistant, resultMap, resultType, id) ;
    }

    protected List<ResultMap> getStatementResultMaps(MapperBuilderAssistant assistant, String resultMap, Class resultType, String id) {
        return QuerySupportUtils.getStatementResultMaps(assistant, resultMap, resultType, id) ;
    }


    protected boolean hasMappedStatement(String mappedStatement) {
        return this.configuration.hasStatement(mappedStatement, false);
    }

    /**
     * 移除之前所注册的mappedStatement
     *
     * @param statementName mapper方法所对应的id
     */
    protected void removeMappedStatement(String statementName) {
        try {
            if (hasMappedStatement(statementName)) {
                configuration.getMappedStatementNames().remove(statementName);
            }
        } catch (Exception e) {
            logger.warn("Query Support remove mapped statement error, cause by : {}", e.getMessage());
        }
    }

    protected MapperBuilderAssistant getMapperBuilderAssistant(Class<?> mapperClass) {
        return mapperClassBuilderAssistantMap.get(mapperClass);
    }

    @Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        /**
         * 记录mapper相关数据
         */
        QuerySupportProperty property = new QuerySupportProperty(mapperClass, modelClass, tableInfo);
        propertyList.add(property);
        mapperClassBuilderAssistantMap.put(mapperClass, this.builderAssistant);
        return null;
    }

}